package com.kingyea.mobilepolice.apimanage.systemsync;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.kingyea.mobilepolice.apimanage.systemsync.bean.InvokedMethod;
import com.kingyea.mobilepolice.apimanage.utils.ApplicationContextUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ReflectionUtils;

import javax.annotation.Resource;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.List;

/**
 * @author cxc
 * @version v1.0.0
 * @description This is a class
 * @date Created in 2018.04.20 11:34
 */

public class SyncInvokMethodProxy {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncInvokMethodProxy.class);
    /**
     * 代理对象缓存
     */
    private static HashMap<String, Object> proxyCacheMap = Maps.newHashMap();
    private static ReferenceQueue queue = new ReferenceQueue();
    private static ProxyFactory cglibProxy = new ProxyFactory();
    private Object proxyObj;// todo 这两个变量是否需要做成map来管理，考虑多个？
    private Class targetClass;

    public SyncInvokMethodProxy() {
    }

    /**
     * 指定 使用的代理对象，对于 指定的代理类型
     */
    public SyncInvokMethodProxy(Object proxyObj, Class targetClass) {
        this.proxyObj = proxyObj;
        this.targetClass = targetClass;
    }

    /**
     * 执行service调用
     *
     * @param invokedMethodObj
     * @return void
     * @author Mr.Lin
     * @date 2018/3/2 9:50
     */
    public void invokService(final InvokedMethod invokedMethodObj) throws Exception {
        Class proxyClass = null;
        if (invokedMethodObj == null) {
            return;
        }
        try {
            /* 进行反射操作调用相应的方法 */
            final InvokedMethod invokedMethod = invokedMethodObj;
            final String clazzName = invokedMethod.getClazzName();
            final String methodName = invokedMethod.getMethodName();

            String beanName = getClazzName(clazzName);

            /** 是否是目标代理类，从spring容器中获取到对象 */
            final Object springBean = (this.targetClass != null && this.proxyObj != null && this.targetClass.getCanonicalName().equals(clazzName))
                    ? this.proxyObj : ApplicationContextUtil.getBean(beanName);
            final Class<? extends Object> clazz = springBean.getClass();

            Object springBean_Proxy = myProxyObj(springBean);
            proxyClass = springBean_Proxy.getClass();

            /** 执行无参反射操作 */
            if (invokedMethod.getAttributeNumber() == null || invokedMethod.getAttributeNumber().intValue() == 0) {
                final Method findMethod = ReflectionUtils.findMethod(clazz, methodName);
                ReflectionUtils.invokeMethod(findMethod, springBean_Proxy);
            } else {
                /** 执行有参反射操作 */
                // 1.准备调用参数列表
                final Object[] params = invokedMethod.getParams();
                // 2.获取参数类型列表
                final Class<?>[] paramTypes = this.getMethodParamTypes(springBean, methodName, invokedMethod.getTypeStrList());
                // 3.查找所要调用的方法
                final Method findMethod = ReflectionUtils.findMethod(clazz, methodName, paramTypes);
                // 防止 ClassCastException ? 来一波类型转换吧  。 因为这个对象被fastJson序列化成文本了
                for (int i = 0; i < paramTypes.length; i++) {
                    Class<?> paramType = paramTypes[i];
                    params[i] = JSON.parseObject(JSON.toJSONString(params[i]), paramType);
                }

                /** 执行 */
                ReflectionUtils.invokeMethod(findMethod, springBean_Proxy, params);
            }
            LOGGER.info("同步事件操作完成，事件{}", JSON.toJSONString(invokedMethod));
        } catch (final Exception e) {
            LOGGER.error("反射调用失败，同步操作未完成，事件", e);
            throw e;
        } finally {
            if (proxyClass != null) {
                ClassLoader loader = proxyClass.getClassLoader();
            }
//            loader = null;
            PhantomReference pr = new PhantomReference(proxyClass, queue);
            System.gc();
            System.out.println("........................................");
        }
    }

    /**
     * 获取cglib代理对象，并且所有服务层都重新代理，并且内部bean自动注入
     */
    private Object myProxyObj(Object sourceObj) throws Exception {
        Object proxyObj = cglibProxyObj(sourceObj);

        Class sClazz = sourceObj.getClass();
        Class pClazz = proxyObj.getClass();

        Field[] sFields = sClazz.getSuperclass().getDeclaredFields();
        Field[] pFields = pClazz.getSuperclass().getSuperclass().getDeclaredFields();

        for (int i = 0; i < sFields.length; i++) {
            Field sField = sFields[i];
            if (ReflectionUtils.isPublicStaticFinal(sField) || (!sField.isAnnotationPresent(Autowired.class) && !sField.isAnnotationPresent(Resource.class))) {
                continue;
            }
            for (int j = 0; j < pFields.length; j++) {
                Field pField = pFields[j];
                pField.setAccessible(true);
                if (sField.getType().getName().equals(pField.getType().getName()) && sField.getName().equals(pField.getName())) {
                    if (ReflectionUtils.isPublicStaticFinal(sField) || (!sField.isAnnotationPresent(Autowired.class) && !sField.isAnnotationPresent(Resource.class))) {
                        continue;
                    }
                    //将字段的访问权限设为true：即去除private修饰符的影响
                    sField.setAccessible(true);
                    Object sFieldObj = sField.get(sourceObj);
                    if (sFieldObj == null) {
                        sFieldObj = ApplicationContextUtil.getBean(sField.getType());
                        if (sField.getName().endsWith("Service") || sField.getName().endsWith("ServiceImpl")) {
                            sFieldObj = myProxyObj(sFieldObj);
                        }
                    }
                    /*去除final修饰符的影响，将字段设为可修改的*/
                    Field modifiersField = Field.class.getDeclaredField("modifiers");
                    modifiersField.setAccessible(true);
                    modifiersField.setInt(pField, pField.getModifiers() & ~Modifier.FINAL);
                    pField.set(proxyObj, sFieldObj);

                    break;
                }
            }
        }
        return proxyObj;
    }

    private String getClazzName(String clazzName) {
        // 对请求的clazzname进行截取数据,拼接成首字母小写，获取容器中的bean
        final String[] split = clazzName.split("\\.");
        String beanName = split[split.length - 1];
        // 对首字母进行转小写
        final StringBuilder strBuilder = new StringBuilder();
        strBuilder.append(Character.toLowerCase(beanName.charAt(0)));
        strBuilder.append(beanName.substring(1));
        beanName = strBuilder.toString();
        return beanName;
    }

    /**
     * 获取cglib代理对象
     */
    private Object cglibProxyObj(Object sourceObj) {
        Class sClazz = sourceObj.getClass();
        String className = sClazz.getCanonicalName();
        String classKey = className.substring(0, className.indexOf("$"));
        Object springBean_Proxy = proxyCacheMap.get(classKey);
        if (springBean_Proxy == null) {
            springBean_Proxy = cglibProxy.createProxy(sourceObj);
            proxyCacheMap.put(classKey, springBean_Proxy);
        }
        return springBean_Proxy;
    }

    /**
     * 根据方法名称取得反射方法的参数类型
     *
     * @param classInstance 类实例
     * @param methodName    方法名
     * @return java.lang.Class<?>[]
     * @author Mr.Lin
     * @date 2018/3/1 23:33
     */
    public Class<?>[] getMethodParamTypes(final Object classInstance, final String methodName, final List<String> typeStrList) {
        Class<?>[] paramTypes = null;
        if (typeStrList == null || typeStrList.size() <= 0) {
            return paramTypes;
        }
        final Method[] methods = classInstance.getClass().getMethods();// 所有方法
        for (int i = 0; i < methods.length; i++) {
            if (methodName.equals(methods[i].getName())) {
                // 和传入方法名匹配
                final Class<?>[] parameterTypes = methods[i].getParameterTypes();
                if (checkParamsType(parameterTypes, typeStrList)) {
                    // 判断该方法形参是否符合参数列表
                    paramTypes = new Class[parameterTypes.length];
                    for (int j = 0; j < parameterTypes.length; j++) {
                        try {
                            paramTypes[j] = Class.forName(parameterTypes[j].getCanonicalName());
                        } catch (final ClassNotFoundException e) {
                            paramTypes[j] = baseTypeConvert(e.getMessage());
                            if (parameterTypes[j] == null) {
                                e.printStackTrace();
                            }
                        }
                    }
                    break;
                }
            }
        }
        return paramTypes;
    }

    /**
     * 检查 parameterTypes参数类型数组 是否跟 typeStrList 参数类型列表内相符
     *
     * @param parameterTypes
     * @param typeStrList
     * @return boolean
     * @author Mr.Lin
     * @date 2018/3/2 14:07
     */
    public boolean checkParamsType(Class<?>[] parameterTypes, List<String> typeStrList) {
        boolean tempFalg = true;
        if (parameterTypes.length == typeStrList.size()) {
            for (int i = 0; i < parameterTypes.length; i++) {
                if (tempFalg) {
                    tempFalg = parameterTypes[i].getCanonicalName().equals(typeStrList.get(i)) ? true : false;
                } else {
                    return false;
                }
            }
            if (tempFalg) {
                return tempFalg;
            }
        }
        return false;
    }

    /**
     * 基本类型格式化
     *
     * @param className
     * @return java.lang.Class
     * @author Mr.Lin
     * @date 2018/4/23 22:57
     */
    public Class baseTypeConvert(String className) {
        if (className.equalsIgnoreCase(int.class.getCanonicalName())) return int.class;
        if (className.equalsIgnoreCase(float.class.getCanonicalName())) return float.class;
        if (className.equalsIgnoreCase(double.class.getCanonicalName())) return double.class;
        if (className.equalsIgnoreCase(long.class.getCanonicalName())) return long.class;
        if (className.equalsIgnoreCase(char.class.getCanonicalName())) return char.class;
        if (className.equalsIgnoreCase(boolean.class.getCanonicalName())) return boolean.class;
        if (className.equalsIgnoreCase(byte.class.getCanonicalName())) return byte.class;
        if (className.equalsIgnoreCase(short.class.getCanonicalName())) return short.class;
        // 数组
        if (className.equalsIgnoreCase(int[].class.getCanonicalName())) return int[].class;
        if (className.equalsIgnoreCase(float[].class.getCanonicalName())) return float[].class;
        if (className.equalsIgnoreCase(double[].class.getCanonicalName())) return double[].class;
        if (className.equalsIgnoreCase(long[].class.getCanonicalName())) return long[].class;
        if (className.equalsIgnoreCase(char[].class.getCanonicalName())) return char[].class;
        if (className.equalsIgnoreCase(boolean[].class.getCanonicalName())) return boolean[].class;
        if (className.equalsIgnoreCase(byte[].class.getCanonicalName())) return byte[].class;
        if (className.equalsIgnoreCase(short[].class.getCanonicalName())) return short[].class;
        // 数组2
        if (className.equalsIgnoreCase(int[][].class.getCanonicalName())) return int[][].class;
        if (className.equalsIgnoreCase(float[][].class.getCanonicalName())) return float[][].class;
        if (className.equalsIgnoreCase(double[][].class.getCanonicalName())) return double[][].class;
        if (className.equalsIgnoreCase(long[][].class.getCanonicalName())) return long[][].class;
        if (className.equalsIgnoreCase(char[][].class.getCanonicalName())) return char[][].class;
        if (className.equalsIgnoreCase(boolean[][].class.getCanonicalName())) return boolean[][].class;
        if (className.equalsIgnoreCase(byte[][].class.getCanonicalName())) return byte[][].class;
        if (className.equalsIgnoreCase(short[][].class.getCanonicalName())) return short[][].class;
        return null;
    }


}
